#include "threadPool.h"

extern conn_info client[1024];

void *sign_up_c(void *arg, MYSQL *conn) {
  Para *para = (Para *)arg;
  char *user_name = para->path[0];
  char *user_password = para->path[1];
  if (user_name == NULL || user_password == NULL) {
    fprintf(stderr, "sign up parameter is invaild!\n");
    return NULL;
  }
  pthread_mutex_lock(&para->pool->mutex);

  userInfo u;
  int ret_find = user_table_find(para->pool->user_map, user_name, &u);

  pthread_mutex_unlock(&para->pool->mutex);

  char user_id[30] = {0};
  char salt[20] = {0};
  char encrypt[2048] = {0};
  char prefix[1024] = {0};

  if (ret_find != 0) {
    char sql[4096] = {0};
    sprintf(sql, "select salt,encrypt from user where user_name = '%s'",
            user_name);
    int qret = mysql_query(conn, sql);
    if (qret != 0) {
      fprintf(stderr, "mysql_query: %s\n", mysql_error(conn));
      return NULL;
    }

    MYSQL_RES *table = mysql_store_result(conn);
    if (mysql_num_rows(table) != 0) {
      fprintf(stderr, "user name %s has exist.", user_name);
      send_err(para->netfd, "user name is exist.\n");
      return NULL;
    }

    sprintf(prefix, "/%s", user_name);

    random_salt(salt);

    strcpy(encrypt, crypt(user_password, salt));

    printf("salt: %s\nencrypt: %s\n", salt, encrypt);

    memset(sql, 0, sizeof(sql));
    sprintf(sql,
            "insert into user (user_name,salt,encrypt,prefix) values "
            "('%s','%s','%s','%s')",
            user_name, salt, encrypt, prefix);
    qret = mysql_query(conn, sql);
    if (qret != 0) {
      fprintf(stderr, "mysql_query: %s\n", mysql_error(conn));
      return NULL;
    }

    memset(sql, 0, sizeof(sql));
    sprintf(sql, "select user_id from user where user_name = '%s'", user_name);
    qret = mysql_query(conn, sql);
    if (qret != 0) {
      fprintf(stderr, "mysql_query: %s\n", mysql_error(conn));
    }

    table = mysql_store_result(conn);
    if (mysql_num_rows(table) == 0 || mysql_num_fields(table) == 0) {
      send_err(para->netfd, "user is not exist.");
      fprintf(stderr, "user: %s is not exist.\n", user_name);
      return NULL;
    }

    MYSQL_ROW row = mysql_fetch_row(table);
    char *sql_user_id = row[0];
    strcpy(user_id, sql_user_id);

    memset(sql, 0, sizeof(sql));
    sprintf(sql,
            "insert into vfs (file_name,file_type,user_id,file_path) values "
            "('%s','%s','%s','%s')",
            "/", "d", user_id, "/");
    qret = mysql_query(conn, sql);
    if (qret != 0) {
      fprintf(stderr, "mysql_query: %s\n", mysql_error(conn));
      return NULL;
    }

    printf("user %s sign up successfully.\n", user_name);
    return NULL;
  }
  fprintf(stderr, "user is exist.\n");
  return NULL;
}

void *login_c(void *arg, MYSQL *conn) {
  Para *para = (Para *)arg;
  char *user_name = para->path[0];
  char *user_password = para->path[1];

  if (user_name == NULL || user_password == NULL) {
    fprintf(stderr, "login parameter is invaild!\n");
    send_err(para->netfd, "login parameter is invaild!\n");
    return NULL;
  }
  pthread_mutex_lock(&para->pool->mutex);
  userInfo u;
  int ret_find = user_table_find(para->pool->user_map, user_name, &u);
  pthread_mutex_unlock(&para->pool->mutex);
  char salt[4096] = {0};
  char encrypt[4096] = {0};
  
  if (ret_find != 0) {
    // 哈希表里没找到，就去查数据库
    char sql[4096] = {0};
    sprintf(sql, "select salt,encrypt from user where user_name = '%s'",
            user_name);
    int qret = mysql_query(conn, sql);
    if (qret != 0) {
      fprintf(stderr, "mysql_query: %s\n", mysql_error(conn));
    }

    MYSQL_RES *table = mysql_store_result(conn);
    if (mysql_num_rows(table) == 0 || mysql_num_fields(table) == 0) {
      send_err(para->netfd, "user is not exist.");
      fprintf(stderr, "user: %s is not exist.\n", user_name);
      return NULL;
    }

    MYSQL_ROW row = mysql_fetch_row(table);
    char *sql_salt = row[0];
    char *sql_encrypt = row[1];
    strcpy(salt, sql_salt);
    strcpy(encrypt, sql_encrypt);

  } else {
    strcpy(salt, u.salt);
    strcpy(encrypt, u.encrypt_password);
  }

  char *user_input_encrypt_password = crypt(user_password, salt);
  if (strcmp(user_input_encrypt_password, encrypt) != 0) {
    // printf("%s\n%s\n", user_input_encrypt_password, encrypt);
    send_err(para->netfd, "password is invaild.\n");
    fprintf(stderr, "password is invaild.\n");
    return NULL;
  }

  pthread_mutex_lock(&para->pool->mutex);

  int k = para->index[para->netfd];
  strcpy(client[k].user_name, user_name);
  printf("uuu: %s\n", para->client[k].user_name);
  pthread_mutex_unlock(&para->pool->mutex);

  // 打日志
  printf("user: %s is login, welcome.\n", user_name);

  char msg[4096] = {0};
  sprintf(msg, "%s", get_jwt(user_name));
  printf("jwt: %s\n", msg);

  send_tlv(para->netfd, msg, strlen(msg));
  memset(msg, 0, sizeof(msg));
  sprintf(msg, "welcome %s, you have login successfully.\n", user_name);
  // todo: 可能会引发阻塞，先这样写，之后需要优化此处
  // send_tlv(para->netfd, msg, strlen(msg));

  return NULL;
}

// 签发的jwt由jwt库自动分配内存，如果不再需要了需要调用
// l8w8jwt_free(jwt);释放内存
char *get_jwt(char *user_name) {
  char *jwt;
  size_t jwt_length;

  struct l8w8jwt_encoding_params params;
  l8w8jwt_encoding_params_init(&params);

  params.alg = L8W8JWT_ALG_HS512;

  params.sub = "Authenticate";
  params.iss = user_name;
  params.aud = "User";

  params.iat = l8w8jwt_time(NULL);
  params.exp = l8w8jwt_time(NULL) + 600;

  params.secret_key = (unsigned char *)"$AC_automaton$>>happy@hacking?+!";
  params.secret_key_length = strlen((const char *)params.secret_key);

  params.out = &jwt;
  params.out_length = &jwt_length;

  int r = l8w8jwt_encode(&params);
  return r == L8W8JWT_SUCCESS ? jwt : " (encoding failure) ";
}

int verify_jwt(char *user_name, char *user_jwt) {
  const char screct[] = "$AC_automaton$>>happy@hacking?+!";

  struct l8w8jwt_decoding_params params;
  l8w8jwt_decoding_params_init(&params);

  params.alg = L8W8JWT_ALG_HS512;

  params.jwt = (char *)user_jwt;
  params.jwt_length = strlen(user_jwt);

  params.verification_key = (unsigned char *)screct;
  params.verification_key_length = strlen(screct);

  params.validate_sub = "Authenticate";
  params.validate_iss = user_name;

  params.validate_exp = 1;
  params.exp_tolerance_seconds = 60;

  params.validate_iat = 1;
  params.iat_tolerance_seconds = 60;

  enum l8w8jwt_validation_result validation_result;

  int decode_result = l8w8jwt_decode(&params, &validation_result, NULL, NULL);

  return decode_result == L8W8JWT_SUCCESS && validation_result == L8W8JWT_VALID;
}
